﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using IssueIT.Core.Util;

namespace IssueIT.Core.Configuration.Settings
{
	public interface ISettingsBuilder
	{
		void BuildSettings(ISettings settings);
	}

	public class SettingsBuilder : ISettingsBuilder
	{
		private readonly ISettingsAdapter _adapter;
		private readonly ISettingsNamingConvention _namingConvention;
		private readonly IConversionIssueReporter _issueReporter;
		private readonly IObjectConverter _objectConverter;

		public SettingsBuilder(ISettingsAdapter adapter, ISettingsNamingConvention namingConvention, IConversionIssueReporter issueReporter, IObjectConverter objectConverter)
		{
			_adapter = adapter;
			_namingConvention = namingConvention;
			_issueReporter = issueReporter;
			_objectConverter = objectConverter;
		}

		public void BuildSettings(ISettings settings)
		{
			if( settings==null )
			{
				throw new ArgumentNullException("settings");
			}

			var properties = GetPropertiesFromSettings(settings);

			foreach(var property in properties)
			{
				if( !property.CanWrite )
				{
					continue;
				}

				var stringValue = getStringValueForSetting(property);

				if( string.IsNullOrWhiteSpace(stringValue) )
				{
					if( !property.IsDefined(typeof(OptionalSettingAttribute), true) )
					{
						settings.AddIssue(new ConversionIssue(settings, property, stringValue, null, "Required value missing"));
					}
					continue;
				}

				try
				{
					if( !_objectConverter.CanBeParsed(property.PropertyType) )
					{
						settings.AddIssue(new ConversionIssue(settings, property, stringValue, null, string.Format("Unable to parse type {0}", property.PropertyType)));
						continue;
					}

					var value = _objectConverter.FromString(stringValue, property.PropertyType);
					property.SetValue(settings, value, null);
				}
				catch(Exception ex)
				{
					settings.AddIssue(new ConversionIssue(settings, property, stringValue, ex, "Failed to convert"));
				}
			}

			_issueReporter.ReportIssues(settings);
		}

		protected virtual IEnumerable<PropertyInfo> GetPropertiesFromSettings(ISettings settings)
		{
			return settings
				.GetType()
				.GetProperties(BindingFlags.Instance | BindingFlags.Public)
				.Where(propertyInfo => propertyInfo.CanWrite);
		}

		private string getStringValueForSetting(PropertyInfo property)
		{
			string settingName = getSettingName(property);
			return _adapter.Get(settingName);
		}

		private string getSettingName(PropertyInfo property)
		{
			return _namingConvention.GetName(property);
		}
	}
}